

#include "Obj4d.h"
#include "3dDef.h"

int CObj4d::Reset()
{
	// this function resets the sent object and redies it for 
	// transformations, basically just resets the culled, clipped and
	// backface flags, but here's where you would add stuff
	// to ready any object for the pipeline
	// the object is valid, let's rip it apart polygon by polygon

	// reset object's culled flag
	RESET_BIT(state, OBJECT4DV1_STATE_CULLED);

	// now the clipped and backface flags for the polygons 
	for (int poly = 0; poly < num_polys; poly++)
	{
		// acquire polygon
		POLY4DV1_PTR curr_poly = &plist[poly];

		// first is this polygon even visible?
		if (!(curr_poly->state & POLY4DV1_STATE_ACTIVE))
			continue; // move onto next poly

		// reset clipped and backface flags
		RESET_BIT(curr_poly->state, POLY4DV1_STATE_CLIPPED);
		RESET_BIT(curr_poly->state, POLY4DV1_STATE_BACKFACE);

	} // end for poly

	return 0;

} // end Reset

float CObj4d::Calc_Radius()
{
	// this function computes the average and maximum radius for 
	// sent object and opdates the object data

	// reset incase there's any residue
	avg_radius = 0;
	max_radius = 0;

	// loop thru and compute radius
	for (int vertex = 0; vertex < num_vertices; vertex++)
	{
		// update the average and maximum radius
		float dist_to_vertex = 
			sqrt(vlist_local[vertex][X] * vlist_local[vertex][X] +
				vlist_local[vertex][Y] * vlist_local[vertex][Y] +
				vlist_local[vertex][Z] *  vlist_local[vertex][Z]);

		// accumulate total radius
		avg_radius += dist_to_vertex;

		// update maximum radius   
		if (dist_to_vertex > max_radius)
			max_radius = dist_to_vertex; 

	} // end for vertex

	// finallize average radius computation
	avg_radius /= num_vertices;

	// return max radius
	return max_radius;

} // end Calc_Radius

int CObj4d::Translate(CVector4d& vt) 
{
	// NOTE: Not matrix based
	// this function translates an object without matrices,
	// simply updates the world_pos
	
	world_pos += vt;

	return 0;

} // end Translate

int CObj4d::Scale(CVector4d& vs)
{
	// NOTE: Not matrix based
	// this function scales and object without matrices 
	// modifies the object's local vertex list 
	// additionally the radii is updated for the object

	// for each vertex in the mesh scale the local coordinates by
	// vs on a componentwise basis, that is, sx, sy, sz
	for (int vertex=0; vertex < num_vertices; vertex++)
	{
		vlist_local[vertex].vector[X] *= vs[X];
		vlist_local[vertex].vector[Y] *= vs[Y];
		vlist_local[vertex].vector[Z] *= vs[Z];
		// leave w unchanged, always equal to 1

	} // end for vertex

	// now since the object is scaled we have to do something with 
	// the radii calculation, but we don't know how the scaling
	// factors relate to the original major axis of the object,
	// therefore for scaling factors all ==1 we will simple multiply
	// which is correct, but for scaling factors not equal to 1, we
	// must take the largest scaling factor and use it to scale the
	// radii with since it's the worst case scenario of the new max and
	// average radii

	// find max scaling factor
	float scale = MAX(vs[X], vs[Y]);
	scale = MAX(scale, vs[Z]);

	// now scale
	max_radius *= scale;
	avg_radius *= scale;

	return 0;

} // end Scale

int CObj4d::Transform(	CMatrix44& mt,			// transformation matrix
						int coord_select,		// selects coords to transform
						int transform_basis)	// flags if vector orientation
												// should be transformed too
{
	// this function simply transforms all of the vertices in the local or trans
	// array by the sent matrix

	// what coordinates should be transformed?
	switch(coord_select)
	{
	case TRANSFORM_LOCAL_ONLY:
		{
			// transform each local/model vertex of the object mesh in place
			for (int vertex=0; vertex < num_vertices; vertex++)
			{
				CPoint4d result; // hold result of each transformation

				// transform point
				Mat_Mul_VECTOR4D_4X4(vlist_local[vertex], mt, result);

				// store result back
				vlist_local[vertex] = result;
			} // end for index
		} break;

	case TRANSFORM_TRANS_ONLY:
		{
			// transform each "transformed" vertex of the object mesh in place
			// remember, the idea of the vlist_trans[] array is to accumulate
			// transformations
			for (int vertex=0; vertex < num_vertices; vertex++)
			{
				CPoint4d result; // hold result of each transformation

				// transform point
				Mat_Mul_VECTOR4D_4X4(vlist_trans[vertex], mt, result);

				// store result back
				vlist_trans[vertex] = result; 
			} // end for index

		} break;

	case TRANSFORM_LOCAL_TO_TRANS:
		{
			// transform each local/model vertex of the object mesh and store result
			// in "transformed" vertex list
			for (int vertex=0; vertex < num_vertices; vertex++)
			{
				CPoint4d result; // hold result of each transformation

				// transform point
				Mat_Mul_VECTOR4D_4X4(vlist_local[vertex], mt, vlist_trans[vertex]);

			} // end for index
		} break;

	default: break;

	} // end switch

	// finally, test if transform should be applied to orientation basis
	// hopefully this is a rotation, otherwise the basis will get corrupted
	if (transform_basis)
	{
		// now rotate orientation basis for object
		CVector4d vresult; // use to rotate each orientation vector axis

		// rotate ux of basis
		Mat_Mul_VECTOR4D_4X4(ux, mt, vresult);
		ux = vresult; 

		// rotate uy of basis
		Mat_Mul_VECTOR4D_4X4(uy, mt, vresult);
		uy = vresult; 

		// rotate uz of basis
		Mat_Mul_VECTOR4D_4X4(uz, mt, vresult);
		uz = vresult; 
	} // end if

	return 0;

} // end Transform

int CObj4d::Model_To_World(int coord_select)
{
	// NOTE: Not matrix based
	// this function converts the local model coordinates of the
	// sent object into world coordinates, the results are stored
	// in the transformed vertex list (vlist_trans) within the object

	// interate thru vertex list and transform all the model/local 
	// coords to world coords by translating the vertex list by
	// the amount world_pos and storing the results in vlist_trans[]

	if (coord_select == TRANSFORM_LOCAL_TO_TRANS)
	{
		for (int vertex=0; vertex < num_vertices; vertex++)
		{
			// translate vertex
			vlist_trans[vertex] = vlist_local[vertex] + world_pos;
		} // end for vertex
	} // end if local
	else
	{ // TRANSFORM_TRANS_ONLY
		for (int vertex=0; vertex < num_vertices; vertex++)
		{
			// translate vertex
			vlist_trans[vertex] = vlist_trans[vertex] + world_pos;
		} // end for vertex
	} // end else trans

	return 0;

} // end Model_To_World

int CObj4d::World_To_Camera()
{
	// NOTE: this is a matrix based function
	// this function transforms the world coordinates of an object
	// into camera coordinates, based on the sent camera matrix
	// but it totally disregards the polygons themselves,
	// it only works on the vertices in the vlist_trans[] list
	// this is one way to do it, you might instead transform
	// the global list of polygons in the render list since you 
	// are guaranteed that those polys represent geometry that 
	// has passed thru backfaces culling (if any)

	// transform each vertex in the object to camera coordinates
	// assumes the object has already been transformed to world
	// coordinates and the result is in vlist_trans[]
	for (int vertex = 0; vertex < num_vertices; vertex++)
	{
		// transform the vertex by the mcam matrix within the camera
		// it better be valid!
		CPoint4d presult; // hold result of each transformation

		// transform point
		Mat_Mul_VECTOR4D_4X4(vlist_trans[vertex], m_pCam->m_mcam, presult);

		// store result back
		vlist_trans[vertex] = presult;
	} // end for vertex

	return 0;

} // end World_To_Camera

int CObj4d::Camera_To_Perspective()
{
	// NOTE: this is not a matrix based function
	// this function transforms the camera coordinates of an object
	// into perspective coordinates, based on the 
	// sent camera object, but it totally disregards the polygons themselves,
	// it only works on the vertices in the vlist_trans[] list
	// this is one way to do it, you might instead transform
	// the global list of polygons in the render list since you 
	// are guaranteed that those polys represent geometry that 
	// has passed thru backfaces culling (if any)
	// finally this function is really for experimental reasons only
	// you would probably never let an object stay intact this far down
	// the pipeline, since it's probably that there's only a single polygon
	// that is visible! But this function has to transform the whole mesh!

	// transform each vertex in the object to perspective coordinates
	// assumes the object has already been transformed to camera
	// coordinates and the result is in vlist_trans[]
	for (int vertex = 0; vertex < num_vertices; vertex++)
	{
		float z = vlist_trans[vertex][Z];

		// transform the vertex by the view parameters in the camera
		vlist_trans[vertex].SetX(m_pCam->m_view_dist * vlist_trans[vertex][X] / z);
		vlist_trans[vertex].SetY(m_pCam->m_view_dist * vlist_trans[vertex][Y] * m_pCam->m_aspect_ratio / z);
		// z = z, so no change

		// not that we are NOT dividing by the homogenous w coordinate since
		// we are not using a matrix operation for this version of the function 

	} // end for vertex

	return 0;

} // end Camera_To_Perspective

int CObj4d::Convert_From_Homogeneous4D()
{
	// this function convertes all vertices in the transformed
	// vertex list from 4D homogeneous coordinates to normal 3D coordinates
	// by dividing each x,y,z component by w

	for (int vertex = 0; vertex < num_vertices; vertex++)
	{
		// convert to non-homogenous coords
		vlist_trans[vertex].DivByW();
	} // end for vertex

	return 0;

} // end Convert_From_Homogeneous4D

int CObj4d::Perspective_To_Screen()
{
	// NOTE: this is not a matrix based function
	// this function transforms the perspective coordinates of an object
	// into screen coordinates, based on the sent viewport info
	// but it totally disregards the polygons themselves,
	// it only works on the vertices in the vlist_trans[] list
	// this is one way to do it, you might instead transform
	// the global list of polygons in the render list since you 
	// are guaranteed that those polys represent geometry that 
	// has passed thru backfaces culling (if any)
	// finally this function is really for experimental reasons only
	// you would probably never let an object stay intact this far down
	// the pipeline, since it's probably that there's only a single polygon
	// that is visible! But this function has to transform the whole mesh!
	// this function would be called after a perspective
	// projection was performed on the object

	// transform each vertex in the object to screen coordinates
	// assumes the object has already been transformed to perspective
	// coordinates and the result is in vlist_trans[]

	float alpha = (0.5*m_pCam->m_viewport_width-0.5);
	float beta  = (0.5*m_pCam->m_viewport_height-0.5);

	for (int vertex = 0; vertex < num_vertices; vertex++)
	{
		// assumes the vertex is in perspective normalized coords from -1 to 1
		// on each axis, simple scale them to viewport and invert y axis and project
		// to screen

		// transform the vertex by the view parameters in the camera
		vlist_trans[vertex].vector[X] = alpha + alpha*vlist_trans[vertex][X];
		vlist_trans[vertex].vector[Y] = beta  - beta *vlist_trans[vertex][Y];

	} // end for vertex

	return 0;

} // end Perspective_To_Screen

int CObj4d::Camera_To_Perspective_Screen()
{
	// NOTE: this is not a matrix based function
	// this function transforms the camera coordinates of an object
	// into Screen scaled perspective coordinates, based on the 
	// sent camera object, that is, view_dist_h and view_dist_v 
	// should be set to cause the desired (width X height)
	// projection of the vertices, but the function totally 
	// disregards the polygons themselves,
	// it only works on the vertices in the vlist_trans[] list
	// this is one way to do it, you might instead transform
	// the global list of polygons in the render list since you 
	// are guaranteed that those polys represent geometry that 
	// has passed thru backfaces culling (if any)
	// finally this function is really for experimental reasons only
	// you would probably never let an object stay intact this far down
	// the pipeline, since it's probably that there's only a single polygon
	// that is visible! But this function has to transform the whole mesh!
	// finally, the function also inverts the y axis, so the coordinates
	// generated from this function ARE screen coordinates and ready for
	// rendering

	float alpha = (0.5*m_pCam->m_viewport_width-0.5);
	float beta  = (0.5*m_pCam->m_viewport_height-0.5);

	// transform each vertex in the object to perspective screen coordinates
	// assumes the object has already been transformed to camera
	// coordinates and the result is in vlist_trans[]
	for (int vertex = 0; vertex < num_vertices; vertex++)
	{
		float z = vlist_trans[vertex][Z];

		// transform the vertex by the view parameters in the camera
		vlist_trans[vertex].vector[X] = m_pCam->m_view_dist*vlist_trans[vertex][X]/z;
		vlist_trans[vertex].vector[Y] = m_pCam->m_view_dist*vlist_trans[vertex][Y]/z;
		// z = z, so no change

		// not that we are NOT dividing by the homogenous w coordinate since
		// we are not using a matrix operation for this version of the function 

		// now the coordinates are in the range x:(-viewport_width/2 to viewport_width/2)
		// and y:(-viewport_height/2 to viewport_height/2), thus we need a translation and
		// since the y-axis is inverted, we need to invert y to complete the screen 
		// transform:
		vlist_trans[vertex].vector[X] =  vlist_trans[vertex][X] + alpha;
		vlist_trans[vertex].vector[Y] = -vlist_trans[vertex][Y] + beta;

	} // end for vertex

	return 0;

} // end Camera_To_Perspective_Screen

int CObj4d::Cull(int cull_flags)     // clipping planes to consider
{
	// NOTE: is matrix based
	// this function culls an entire object from the viewing
	// frustrum by using the sent camera information and object
	// the cull_flags determine what axes culling should take place
	// x, y, z or all which is controlled by ORing the flags
	// together
	// if the object is culled its state is modified thats all
	// this function assumes that both the camera and the object
	// are valid!

	// step 1: transform the center of the object's bounding
	// sphere into camera space

	CPoint4d sphere_pos; // hold result of transforming center of bounding sphere

	// transform point
	Mat_Mul_VECTOR4D_4X4(world_pos, m_pCam->m_mcam, sphere_pos);

	// step 2:  based on culling flags remove the object
	if (cull_flags & CULL_OBJECT_Z_PLANE)
	{
		// cull only based on z clipping planes

		// test far plane
		if ( ((sphere_pos[Z] - max_radius) > m_pCam->m_far_clip_z) ||
			((sphere_pos[Z] + max_radius) < m_pCam->m_near_clip_z) )
		{ 
			SET_BIT(state, OBJECT4DV1_STATE_CULLED);
			return 0;
		} // end if

	} // end if

	if (cull_flags & CULL_OBJECT_X_PLANE)
	{
		// cull only based on x clipping planes
		// we could use plane equations, but simple similar triangles
		// is easier since this is really a 2D problem
		// if the view volume is 90 degrees the the problem is trivial
		// buts lets assume its not

		// test the the right and left clipping planes against the leftmost and rightmost
		// points of the bounding sphere
		float z_test = (0.5) * m_pCam->m_viewplane_width * sphere_pos[Z] / m_pCam->m_view_dist;

		if ( ((sphere_pos[X] - max_radius) > z_test)  || // right side
			((sphere_pos[X] + max_radius) < -z_test) )  // left side, note sign change
		{ 
			SET_BIT(state, OBJECT4DV1_STATE_CULLED);
			return 0;
		} // end if
	} // end if

	if (cull_flags & CULL_OBJECT_Y_PLANE)
	{
		// cull only based on y clipping planes
		// we could use plane equations, but simple similar triangles
		// is easier since this is really a 2D problem
		// if the view volume is 90 degrees the the problem is trivial
		// buts lets assume its not

		// test the the top and bottom clipping planes against the bottommost and topmost
		// points of the bounding sphere
		float z_test = (0.5) * m_pCam->m_viewplane_height * sphere_pos[Z] / m_pCam->m_view_dist;

		if ( ((sphere_pos[Y] - max_radius) > z_test)  || // top side
			((sphere_pos[Y] + max_radius) < -z_test) )  // bottom side, note sign change
		{ 
			SET_BIT(state, OBJECT4DV1_STATE_CULLED);
			return 0;
		} // end if

	} // end if

	// return failure to cull
	return -1;

} // end Cull

int CObj4d::Remove_Backfaces()
{
	// NOTE: this is not a matrix based function
	// this function removes the backfaces from an object's
	// polygon mesh, the function does this based on the vertex
	// data in vlist_trans along with the camera position (only)
	// note that only the backface state is set in each polygon

	// test if the object is culled
	if (state & OBJECT4DV1_STATE_CULLED)
		return 0;

	// process each poly in mesh
	for (int poly=0; poly < num_polys; poly++)
	{
		// acquire polygon
		POLY4DV1_PTR curr_poly = &plist[poly];

		// is this polygon valid?
		// test this polygon if and only if it's not clipped, not culled,
		// active, and visible and not 2 sided. Note we test for backface in the event that
		// a previous call might have already determined this, so why work
		// harder!
		if (!(curr_poly->state & POLY4DV1_STATE_ACTIVE) ||
			(curr_poly->state & POLY4DV1_STATE_CLIPPED ) ||
			(curr_poly->attr  & POLY4DV1_ATTR_2SIDED)    ||
			(curr_poly->state & POLY4DV1_STATE_BACKFACE) )
			continue; // move onto next poly

		// extract vertex indices into master list, rember the polygons are 
		// NOT self contained, but based on the vertex list stored in the object
		// itself
		int vindex_0 = curr_poly->vert[0];
		int vindex_1 = curr_poly->vert[1];
		int vindex_2 = curr_poly->vert[2];

		// we will use the transformed polygon vertex list since the backface removal
		// only makes sense at the world coord stage further of the pipeline 

		// we need to compute the normal of this polygon face, and recall
		// that the vertices are in cw order, u = p0->p1, v=p0->p2, n=uxv
		CVector4d u, v, n;

		// build u, v
		u.Build(vlist_trans[ vindex_0 ], vlist_trans[ vindex_1 ]);
		v.Build(vlist_trans[ vindex_0 ], vlist_trans[ vindex_2 ]);
		

		// compute cross product
		n = u.Cross(v);

		// now create eye vector to viewpoint
		CVector4d view;
		view.Build(vlist_trans[ vindex_0 ], m_pCam->m_pos);
	
		// and finally, compute the dot product
		float dp = n.Dot(view);

		// if the sign is > 0 then visible, 0 = scathing, < 0 invisible
		if (dp <= 0.0 )
			SET_BIT(curr_poly->state, POLY4DV1_STATE_BACKFACE);

	} // end for poly

	return 0;

} // end Remove_Backfaces

int CObj4d::Draw_Wire16()
{
	// this function renders an object to the screen in wireframe, 
	// 16 bit mode, it has no regard at all about hidden surface removal, 
	// etc. the function only exists as an easy way to render an object 
	// without converting it into polygons, the function assumes all 
	// coordinates are screen coordinates, but will perform 2D clipping

	// iterate thru the poly list of the object and simply draw
	// each polygon
	for (int poly=0; poly < num_polys; poly++)
	{
		// render this polygon if and only if it's not clipped, not culled,
		// active, and visible, note however the concecpt of "backface" is 
		// irrelevant in a wire frame engine though
		if (!(plist[poly].state & POLY4DV1_STATE_ACTIVE) ||
			(plist[poly].state & POLY4DV1_STATE_CLIPPED ) ||
			(plist[poly].state & POLY4DV1_STATE_BACKFACE) )
			continue; // move onto next poly

		// extract vertex indices into master list, rember the polygons are 
		// NOT self contained, but based on the vertex list stored in the object
		// itself
		int vindex_0 = plist[poly].vert[0];
		int vindex_1 = plist[poly].vert[1];
		int vindex_2 = plist[poly].vert[2];

		// draw the lines now
		m_graphLine.Draw_Line16(vlist_trans[ vindex_0 ][X], vlist_trans[ vindex_0 ][Y], 
								vlist_trans[ vindex_1 ][X], vlist_trans[ vindex_1 ][Y], 
								plist[poly].color);

		m_graphLine.Draw_Line16(vlist_trans[ vindex_1 ][X], vlist_trans[ vindex_1 ][Y], 
								vlist_trans[ vindex_2 ][X], vlist_trans[ vindex_2 ][Y], 
								plist[poly].color);

		m_graphLine.Draw_Line16(vlist_trans[ vindex_2 ][X], vlist_trans[ vindex_2 ][Y], 
								vlist_trans[ vindex_0 ][X], vlist_trans[ vindex_0 ][Y], 
								plist[poly].color);

		// track rendering stats
#ifdef DEBUG_ON
		debug_polys_rendered_per_frame++;
#endif


	} // end for poly

	return 0;

} // end Draw_Wire16
